using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;

namespace Abide_Halo_Library.Halo_2_Map
{
    public class Halo2Map : MarshalByRefObject, IDisposable
    {
        public IO.HaloIO IO
        {
            get { return io; }
            set { io = value; }
        }
        public Int32 Version
        {
            get { return this.mHeader.version; }
        }
        public Int32 FileSize
        {
            get { return this.mHeader.fileSize; }
        }
        public Int32 IndexOffset
        {
            get { return this.mHeader.indexOffset; }
        }
        public Int32 IndexSize
        {
            get { return this.mHeader.indexSize; }
        }
        public Int32 MetaSize
        {
            get { return this.mHeader.metaSize; }
        }
        public Int32 NonRawSize
        {
            get { return this.mHeader.nonRawSize; }
        }
        public String Origin
        {
            get
            {
                String ret = String.Empty;

                for (int i = 0; i < this.mHeader.mapOrigin.Length; i++)
                {
                    if (this.mHeader.mapOrigin[i] != '\0')
                        ret += this.mHeader.mapOrigin[i];
                }

                return ret;
            }
        }
        public String Build
        {
            get
            {
                String ret = String.Empty;

                for (int i = 0; i < this.mHeader.build.Length; i++)
                {
                    if (this.mHeader.build[i] != '\0')
                        ret += this.mHeader.build[i];
                }

                return ret;
            }
        }
        public Int32 Crazy
        {
            get { return this.mHeader.crazyOffset; }
        }
        public Int32 CrazySize
        {
            get { return this.mHeader.crazySize; }
        }
        public Int32 Strings128TableOffset
        {
            get { return this.mHeader.strings128TableOffset; }
        }
        public Int32 StringsCount
        {
            get { return this.mHeader.stringsCount; }
        }
        public Int32 StringTableSize
        {
            get { return this.mHeader.stringTableSize; }
        }
        public Int32 StringTableIndexOffset
        {
            get { return this.mHeader.stringTableIndexOffset; }
        }
        public Int32 StringTableOffset
        {
            get { return this.mHeader.stringTableOffset; }
        }
        public String MapName
        {
            get
            {
                return new string(mHeader.mapName).Replace("\0", string.Empty);
            }
        }
        public String ScenarioPath
        {
            get
            {
                String ret = String.Empty;

                for (int i = 0; i < this.mHeader.scenarioPath.Length; i++)
                {
                    if (this.mHeader.scenarioPath[i] != '\0')
                        ret += this.mHeader.scenarioPath[i];
                }

                return ret;
            }
        }
        public Int32 FileCount
        {
            get { return this.mHeader.fileCount; }
        }
        public Int32 FileTableSize
        {
            get { return this.mHeader.fileTableSize; }
        }
        public Int32 FileTableOffset
        {
            get { return this.mHeader.fileTableOffset; }
        }
        public Int32 FilesIndexOffset
        {
            get { return this.mHeader.filesIndex; }
        }
        public Int32 Signature
        {
            get { return this.mHeader.signature; }
        }
        public Int32 PrimaryMagic
        {
            get { return this.mIHeader.primaryMagicConstant - this.mIHeader.indexHeaderOffset + 32; }
        }
        public Int32 SecondaryMagic
        {
            get { return this.mIHeader.matgOffset - this.mIHeader.metaStart; }
        }
        public Int32 TagListCount
        {
            get { return this.mIHeader.tagListCount; }
        }
        public Int32 ObjectIndexOffset
        {
            get { return this.mIHeader.objectIndexOffset; }
        }
        public Int32 ScenarioID
        {
            get { return this.mIHeader.scenarioID; }
        }
        public Int32 GlobalsID
        {
            get { return this.mIHeader.matchGlobalsID; }
        }
        public Int32 ObjectCount
        {
            get { return this.mIHeader.objectCount; }
        }
        public Int32 ObjectStart
        {
            get { return this.mIHeader.objectStart; }
        }
        public IndexEntry[] IndexEntries
        {
            get { return this.indexEntries; }
        }
        public String[] StringIDs
        {
            get { return stringIDs; }
        }
        public String[] String128s
        {
            get { return string128s; }
        }

        IO.HaloIO io;
        MAP_HEADER mHeader;
        INDEX_TABLE_HEADER mIHeader;
        TAG_LIST_ENTRY[] tags;
        OBJECT_ENTRY[] objects;
        IndexEntry[] indexEntries;
        IndexEntry scenarioEntry;
        String[] fileList;
        String[] stringIDs;
        String[] string128s;

        public IndexEntry this[int ident]
        {
            get { return GetEntryByIdent(ident); }
        }
        public IndexEntry this[string fileName, string className]
        {
            get { return GetEntryByFilename(string.Format("{0}.{1}", fileName, className)); }
        }

        public Halo2Map() { }
        public void Dispose()
        {
            io.Dispose();
        }
        public void LoadMap(String fileLocation)
        {
            //Check if Nulled...
            if (this.io != null)
            {
                this.io.CloseIO();
                this.io.Dispose();
            }

            //Prepare new IO Handler
            this.io = new IO.HaloIO(fileLocation);
            this.io.OpenIO();

            //Prepare Header
            #region Load H2 Header
            this.mHeader = new MAP_HEADER();
            this.io.Position = 0;
            this.io.MapStream.Position = 0L;
            this.mHeader.head = this.io.In.ReadChars(4);
            this.mHeader.version = this.io.In.ReadInt32();					//4
            this.mHeader.fileSize = this.io.In.ReadInt32();					//8
            this.mHeader.zero_1 = this.io.In.ReadInt32();					//12
            this.mHeader.indexOffset = this.io.In.ReadInt32();				//16
            this.mHeader.indexSize = this.io.In.ReadInt32();				//20
            this.mHeader.metaSize = this.io.In.ReadInt32();					//24
            this.mHeader.nonRawSize = this.io.In.ReadInt32();				//28
            this.mHeader.mapOrigin = this.io.In.ReadChars(256);				//32
            this.mHeader.build = this.io.In.ReadChars(32);					//288
            this.mHeader.unknown_3 = this.io.In.ReadBytes(20);				//320
            this.mHeader.unknown_4 = this.io.In.ReadInt32();				//340
            this.mHeader.crazyOffset = this.io.In.ReadInt32();				//344
            this.mHeader.crazySize = this.io.In.ReadInt32();				//348
            this.mHeader.strings128TableOffset = this.io.In.ReadInt32();	//352
            this.mHeader.stringsCount = this.io.In.ReadInt32();				//356
            this.mHeader.stringTableSize = this.io.In.ReadInt32();			//360
            this.mHeader.stringTableIndexOffset = this.io.In.ReadInt32();	//364
            this.mHeader.stringTableOffset = this.io.In.ReadInt32();		//368
            this.mHeader.unknown_6 = this.io.In.ReadBytes(36);				//372
            this.mHeader.mapName = this.io.In.ReadChars(32);				//408
            this.mHeader.zero_2 = this.io.In.ReadInt32();					//440
            this.mHeader.scenarioPath = this.io.In.ReadChars(32);			//444
            this.mHeader.zero_3 = this.io.In.ReadBytes(224);				//476
            this.mHeader.unknown_7 = this.io.In.ReadInt32();				//700
            this.mHeader.fileCount = this.io.In.ReadInt32();				//704
            this.mHeader.fileTableOffset = this.io.In.ReadInt32();			//708
            this.mHeader.fileTableSize = this.io.In.ReadInt32();			//712
            this.mHeader.filesIndex = this.io.In.ReadInt32();				//716
            this.mHeader.signature = this.io.In.ReadInt32();				//720
            this.mHeader.zero_4 = this.io.In.ReadBytes(1064);				//724
            this.mHeader.foot = this.io.In.ReadChars(4);					//1788
            #endregion

            //Prepare Index Table Header
            #region H2 Index Table Header
            this.io.Position = this.mHeader.indexOffset;
            this.mIHeader.metaStart = this.mHeader.indexOffset + this.mHeader.indexSize;
            this.mIHeader.indexHeaderOffset = this.mHeader.indexOffset;
            this.mIHeader.primaryMagicConstant = this.io.In.ReadInt32();
            this.mIHeader.tagListCount = this.io.In.ReadInt32();
            this.mIHeader.objectIndexOffset = this.io.In.ReadInt32();
            this.mIHeader.scenarioID = this.io.In.ReadInt32();
            this.mIHeader.matchGlobalsID = this.io.In.ReadInt32();
            this.mIHeader.unknown = this.io.In.ReadInt32();
            this.mIHeader.objectCount = this.io.In.ReadInt32();
            this.mIHeader.tags = this.io.In.ReadChars(4);
            this.mIHeader.objectStart = (this.mHeader.indexOffset + 32) + (12 * this.mIHeader.tagListCount);
            this.io.Position = mIHeader.objectStart + 8;
            this.mIHeader.matgOffset = this.io.In.ReadInt32();
            #endregion

            //Read Tag List
            #region Read Tag List
            this.io.Position = this.mHeader.filesIndex;
            int[] fileOffsets = new int[this.mHeader.fileCount];
            for (int i = 0; i < this.mHeader.fileCount; i++)
                fileOffsets[i] = this.io.In.ReadInt32();
            fileList = new string[mHeader.fileCount];
            for (int i = 0; i < this.mHeader.fileCount; i++)
            {
                this.io.Position = fileOffsets[i] + this.mHeader.fileTableOffset;
                string file = string.Empty;
                char c = this.io.In.ReadChar();
                while (c != '\0')
                {
                    file += c;
                    c = io.In.ReadChar();
                }
                fileList[i] = file;
            }
            #endregion

            //Read Tag Items
            #region Read Tag Items
            tags = new TAG_LIST_ENTRY[this.mIHeader.tagListCount];
            for (int i = 0; i < this.mIHeader.tagListCount; i++)
            {
                tags[i] = new TAG_LIST_ENTRY();
                this.io.Position = (this.mHeader.indexOffset + 32) + (i * 12);

                tags[i].objectClass = this.io.In.ReadChars(4);
                tags[i].parentClass = this.io.In.ReadChars(4);
                tags[i].grandparentClass = this.io.In.ReadChars(4);
            }
            #endregion

            //Read Objects and create Index Entries
            #region Read Objects and create Index Entries
            string fileName = null;
            objects = new OBJECT_ENTRY[this.mIHeader.objectCount];
            indexEntries = new IndexEntry[this.mIHeader.objectCount];
            for (int i = 0; i < this.mIHeader.objectCount; i++)
            {
                objects[i] = new OBJECT_ENTRY();
                IO.MapStream.Position = this.mIHeader.objectStart + (16 * i);

                objects[i].tag = this.io.In.ReadChars(4);
                objects[i].id = this.io.In.ReadInt32();
                objects[i].offset = this.io.In.ReadInt32();
                objects[i].metaLength = this.io.In.ReadInt32();

                fileName = FileCount <= i ? string.Empty : fileList[i];
                indexEntries[i] = new IndexEntry(objects[i], this.mIHeader.matgOffset - this.mIHeader.metaStart, fileName);
                indexEntries[i].Magic = SecondaryMagic;
            }
            #endregion

            //Load String IDs
            #region Read String IDs
            this.io.Position = this.mHeader.stringTableOffset;
            String[] falseStrings = new String(io.In.ReadChars(this.mHeader.stringTableSize)).Split('\0');
            stringIDs = new String[this.mHeader.stringsCount];
            #endregion

            //Load String 128s
            #region Read String 128s
            this.io.Position = this.mHeader.strings128TableOffset;
            String falseStrings128 = new String(io.In.ReadChars(128 * this.mHeader.stringsCount));
            string128s = new String[this.mHeader.stringsCount];
            #endregion

            //Fix Strings
            #region Fix Strings
            for (int i = 0; i < this.mHeader.stringsCount; i++)
            {
                //String ID's
                StringIDs[i] = falseStrings[i];

                //String 128's
                Int32 offset128 = 128 * i;

                for (int x = 0; x < 128; x++)
                {
                    if (falseStrings128[offset128 + x] != '\0')
                        string128s[i] += falseStrings128[offset128 + x];
                }
            }
            #endregion

            //Load SBSP  and LTMP Data
            #region Load SBSP and LTMP Data
            this.scenarioEntry = GetEntryByIdent(ScenarioID);
            int Offset = scenarioEntry.Offset + 528; this.io.Position = Offset;
            int Count = this.io.In.ReadInt32(); int Pointer = this.io.In.ReadInt32();
            int TagBlockOffset = Pointer - SecondaryMagic;
            for (int i = 0; i < Count; i++)
            {
                //Go to Position
                this.io.Position = TagBlockOffset + (i * 68);

                //Get SBSP Data
                int SBSP_Offset = this.io.In.ReadInt32();
                int SBSP_Size = this.io.In.ReadInt32();
                int Magic = this.io.In.ReadInt32() - SBSP_Offset;
                this.io.Position += 8;
                int sbspId = this.io.In.ReadInt32();

                //Set SBSP Entry
                if (sbspId != -1)
                {
                    Int32 SbspIdIndex = GetIndexByIdent(sbspId);
                    indexEntries[SbspIdIndex].Offset = SBSP_Offset;
                    indexEntries[SbspIdIndex].Size = SBSP_Size;
                    indexEntries[SbspIdIndex].Magic = Magic;
                }

                //Get LTMP Ident
                this.io.Position = TagBlockOffset + (i * 68) + 28;
                int ltmpId = this.io.In.ReadInt32();
                this.io.Position = SBSP_Offset + 8;

                //Get LTMP Data
                int LTMP_Offset = 0;
                int LTMP_Size = 0;
                if (SBSP_Offset != 0 && SBSP_Offset != -1)
                {
                    LTMP_Offset = this.io.In.ReadInt32() - Magic;
                    LTMP_Size = SBSP_Size + SBSP_Offset - LTMP_Offset;
                }

                //Set LTMP Entry
                if (ltmpId != -1)
                {
                    int LtmpIdIndex = GetIndexByIdent(ltmpId);
                    indexEntries[LtmpIdIndex].Offset = LTMP_Offset;
                    indexEntries[LtmpIdIndex].Size = LTMP_Size;
                    indexEntries[LtmpIdIndex].Magic = Magic;
                }
            }
            #endregion

            //Close Map
            this.io.CloseIO();
        }
        public void LoadMap(Stream stream)
        {
            //Check if Nulled...
            if (this.io != null)
            {
                this.io.CloseIO();
                this.io.Dispose();
            }

            //Prepare new IO Handler
            this.io = new IO.HaloIO(stream);
            this.io.OpenIO();

            //Prepare Header
            #region Load H2 Header
            this.mHeader = new MAP_HEADER();
            this.io.Position = 0;
            this.io.MapStream.Position = 0L;
            this.mHeader.head = this.io.In.ReadChars(4);
            this.mHeader.version = this.io.In.ReadInt32();					//4
            this.mHeader.fileSize = this.io.In.ReadInt32();					//8
            this.mHeader.zero_1 = this.io.In.ReadInt32();					//12
            this.mHeader.indexOffset = this.io.In.ReadInt32();				//16
            this.mHeader.indexSize = this.io.In.ReadInt32();				//20
            this.mHeader.metaSize = this.io.In.ReadInt32();					//24
            this.mHeader.nonRawSize = this.io.In.ReadInt32();				//28
            this.mHeader.mapOrigin = this.io.In.ReadChars(256);				//32
            this.mHeader.build = this.io.In.ReadChars(32);					//288
            this.mHeader.unknown_3 = this.io.In.ReadBytes(20);				//320
            this.mHeader.unknown_4 = this.io.In.ReadInt32();				//340
            this.mHeader.crazyOffset = this.io.In.ReadInt32();				//344
            this.mHeader.crazySize = this.io.In.ReadInt32();				//348
            this.mHeader.strings128TableOffset = this.io.In.ReadInt32();	//352
            this.mHeader.stringsCount = this.io.In.ReadInt32();				//356
            this.mHeader.stringTableSize = this.io.In.ReadInt32();			//360
            this.mHeader.stringTableIndexOffset = this.io.In.ReadInt32();	//364
            this.mHeader.stringTableOffset = this.io.In.ReadInt32();		//368
            this.mHeader.unknown_6 = this.io.In.ReadBytes(36);				//372
            this.mHeader.mapName = this.io.In.ReadChars(32);				//408
            this.mHeader.zero_2 = this.io.In.ReadInt32();					//440
            this.mHeader.scenarioPath = this.io.In.ReadChars(32);			//444
            this.mHeader.zero_3 = this.io.In.ReadBytes(224);				//476
            this.mHeader.unknown_7 = this.io.In.ReadInt32();				//700
            this.mHeader.fileCount = this.io.In.ReadInt32();				//704
            this.mHeader.fileTableOffset = this.io.In.ReadInt32();			//708
            this.mHeader.fileTableSize = this.io.In.ReadInt32();			//712
            this.mHeader.filesIndex = this.io.In.ReadInt32();				//716
            this.mHeader.signature = this.io.In.ReadInt32();				//720
            this.mHeader.zero_4 = this.io.In.ReadBytes(1064);				//724
            this.mHeader.foot = this.io.In.ReadChars(4);					//1788
            #endregion

            //Prepare Index Table Header
            #region H2 Index Table Header
            this.io.Position = this.mHeader.indexOffset;
            this.mIHeader.metaStart = this.mHeader.indexOffset + this.mHeader.indexSize;
            this.mIHeader.indexHeaderOffset = this.mHeader.indexOffset;
            this.mIHeader.primaryMagicConstant = this.io.In.ReadInt32();
            this.mIHeader.tagListCount = this.io.In.ReadInt32();
            this.mIHeader.objectIndexOffset = this.io.In.ReadInt32();
            this.mIHeader.scenarioID = this.io.In.ReadInt32();
            this.mIHeader.matchGlobalsID = this.io.In.ReadInt32();
            this.mIHeader.unknown = this.io.In.ReadInt32();
            this.mIHeader.objectCount = this.io.In.ReadInt32();
            this.mIHeader.tags = this.io.In.ReadChars(4);
            this.mIHeader.objectStart = (this.mHeader.indexOffset + 32) + (12 * this.mIHeader.tagListCount);
            this.io.Position = mIHeader.objectStart + 8;
            this.mIHeader.matgOffset = this.io.In.ReadInt32();
            #endregion

            //Read Tag List
            #region Read Tag List
            this.io.Position = this.mHeader.filesIndex;
            int[] fileOffsets = new int[this.mHeader.fileCount];
            for (int i = 0; i < this.mHeader.fileCount; i++)
                fileOffsets[i] = this.io.In.ReadInt32();
            fileList = new string[mHeader.fileCount];
            for (int i = 0; i < this.mHeader.fileCount; i++)
            {
                this.io.Position = fileOffsets[i] + this.mHeader.fileTableOffset;
                string file = string.Empty;
                char c = this.io.In.ReadChar();
                while (c != '\0')
                {
                    file += c;
                    c = io.In.ReadChar();
                }
                fileList[i] = file;
            }
            #endregion

            //Read Tag Items
            #region Read Tag Items
            tags = new TAG_LIST_ENTRY[this.mIHeader.tagListCount];
            for (int i = 0; i < this.mIHeader.tagListCount; i++)
            {
                tags[i] = new TAG_LIST_ENTRY();
                this.io.Position = (this.mHeader.indexOffset + 32) + (i * 12);

                tags[i].objectClass = this.io.In.ReadChars(4);
                tags[i].parentClass = this.io.In.ReadChars(4);
                tags[i].grandparentClass = this.io.In.ReadChars(4);
            }
            #endregion

            //Read Objects and create Index Entries
            #region Read Objects and create Index Entries
            string fileName = null;
            objects = new OBJECT_ENTRY[this.mIHeader.objectCount];
            indexEntries = new IndexEntry[this.mIHeader.objectCount];
            for (int i = 0; i < this.mIHeader.objectCount; i++)
            {
                objects[i] = new OBJECT_ENTRY();
                IO.MapStream.Position = this.mIHeader.objectStart + (16 * i);

                objects[i].tag = this.io.In.ReadChars(4);
                objects[i].id = this.io.In.ReadInt32();
                objects[i].offset = this.io.In.ReadInt32();
                objects[i].metaLength = this.io.In.ReadInt32();

                fileName = FileCount <= i ? string.Empty : fileList[i];
                indexEntries[i] = new IndexEntry(objects[i], this.mIHeader.matgOffset - this.mIHeader.metaStart, fileName);
                indexEntries[i].Magic = SecondaryMagic;
            }
            #endregion

            //Load String IDs
            #region Read String IDs
            this.io.Position = this.mHeader.stringTableOffset;
            String[] falseStrings = new String(io.In.ReadChars(this.mHeader.stringTableSize)).Split('\0');
            stringIDs = new String[this.mHeader.stringsCount];
            #endregion

            //Load String 128s
            #region Read String 128s
            this.io.Position = this.mHeader.strings128TableOffset;
            String falseStrings128 = new String(io.In.ReadChars(128 * this.mHeader.stringsCount));
            string128s = new String[this.mHeader.stringsCount];
            #endregion

            //Fix Strings
            #region Fix Strings
            for (int i = 0; i < this.mHeader.stringsCount; i++)
            {
                //String ID's
                StringIDs[i] = falseStrings[i];

                //String 128's
                Int32 offset128 = 128 * i;

                for (int x = 0; x < 128; x++)
                {
                    if (falseStrings128[offset128 + x] != '\0')
                        string128s[i] += falseStrings128[offset128 + x];
                }
            }
            #endregion

            //Load SBSP  and LTMP Data
            #region Load SBSP and LTMP Data
            this.scenarioEntry = GetEntryByIdent(ScenarioID);
            int Offset = scenarioEntry.Offset + 528; this.io.Position = Offset;
            int Count = this.io.In.ReadInt32(); int Pointer = this.io.In.ReadInt32();
            int TagBlockOffset = Pointer - SecondaryMagic;
            for (int i = 0; i < Count; i++)
            {
                //Go to Position
                this.io.Position = TagBlockOffset + (i * 68);

                //Get SBSP Data
                int SBSP_Offset = this.io.In.ReadInt32();
                int SBSP_Size = this.io.In.ReadInt32();
                int Magic = this.io.In.ReadInt32() - SBSP_Offset;
                this.io.Position += 8;
                int sbspId = this.io.In.ReadInt32();

                //Set SBSP Entry
                if (sbspId != -1)
                {
                    Int32 SbspIdIndex = GetIndexByIdent(sbspId);
                    indexEntries[SbspIdIndex].Offset = SBSP_Offset;
                    indexEntries[SbspIdIndex].Size = SBSP_Size;
                    indexEntries[SbspIdIndex].Magic = Magic;
                }

                //Get LTMP Ident
                this.io.Position = TagBlockOffset + (i * 68) + 28;
                int ltmpId = this.io.In.ReadInt32();
                this.io.Position = SBSP_Offset + 8;

                //Get LTMP Data
                int LTMP_Offset = 0;
                int LTMP_Size = 0;
                if (SBSP_Offset != 0 && SBSP_Offset != -1)
                {
                    LTMP_Offset = this.io.In.ReadInt32() - Magic;
                    LTMP_Size = SBSP_Size + SBSP_Offset - LTMP_Offset;
                }

                //Set LTMP Entry
                if (ltmpId != -1)
                {
                    int LtmpIdIndex = GetIndexByIdent(ltmpId);
                    indexEntries[LtmpIdIndex].Offset = LTMP_Offset;
                    indexEntries[LtmpIdIndex].Size = LTMP_Size;
                    indexEntries[LtmpIdIndex].Magic = Magic;
                }
            }
            #endregion

            //Close Map
            this.io.CloseIO();
        }
        public IAsyncResult BeginLoadMap(AsyncCallback callback, String fileLocation, object state)
        {
            //Create
            AsyncOperation loadMapOperation = AsyncOperationManager.CreateOperation(state);
            LoadMapFromFileWorker worker = new LoadMapFromFileWorker(LoadMap);
            return worker.BeginInvoke(fileLocation, callback, worker);
        }
        public IAsyncResult BeginLoadMap(AsyncCallback callback, Stream mapStream, object state)
        {
            //Create
            AsyncOperation loadMapOperation = AsyncOperationManager.CreateOperation(state);
            LoadMapFromStreamWorker worker = new LoadMapFromStreamWorker(LoadMap);
            return worker.BeginInvoke(mapStream, callback, worker);
        }
        public void EndLoadMap(IAsyncResult ar)
        {
            //Get Worker...
            if (ar.AsyncState is LoadMapFromFileWorker)
            {
                //End
                LoadMapFromFileWorker fileLoadWorker = (LoadMapFromFileWorker)ar.AsyncState;
                fileLoadWorker.EndInvoke(ar);
            }
            else if (ar.AsyncState is LoadMapFromStreamWorker)
            {
                //End
                LoadMapFromStreamWorker streamLoadWorker = (LoadMapFromStreamWorker)ar.AsyncState;
                streamLoadWorker.EndInvoke(ar);
            }
        }
        public void Reload()
        {
            //Check if Nulled...
            if (this.io != null)
            {
                this.io.CloseIO();
                this.io.Dispose();
            }

            //Open
            this.io.OpenIO();

            //Prepare Header
            #region Load H2 Header
            this.mHeader = new MAP_HEADER();
            this.io.Position = 0;
            this.io.MapStream.Position = 0L;
            this.mHeader.head = this.io.In.ReadChars(4);
            this.mHeader.version = this.io.In.ReadInt32();					//4
            this.mHeader.fileSize = this.io.In.ReadInt32();					//8
            this.mHeader.zero_1 = this.io.In.ReadInt32();					//12
            this.mHeader.indexOffset = this.io.In.ReadInt32();				//16
            this.mHeader.indexSize = this.io.In.ReadInt32();				//20
            this.mHeader.metaSize = this.io.In.ReadInt32();					//24
            this.mHeader.nonRawSize = this.io.In.ReadInt32();				//28
            this.mHeader.mapOrigin = this.io.In.ReadChars(256);				//32
            this.mHeader.build = this.io.In.ReadChars(32);					//288
            this.mHeader.unknown_3 = this.io.In.ReadBytes(20);				//320
            this.mHeader.unknown_4 = this.io.In.ReadInt32();				//340
            this.mHeader.crazyOffset = this.io.In.ReadInt32();				//344
            this.mHeader.crazySize = this.io.In.ReadInt32();				//348
            this.mHeader.strings128TableOffset = this.io.In.ReadInt32();	//352
            this.mHeader.stringsCount = this.io.In.ReadInt32();				//356
            this.mHeader.stringTableSize = this.io.In.ReadInt32();			//360
            this.mHeader.stringTableIndexOffset = this.io.In.ReadInt32();	//364
            this.mHeader.stringTableOffset = this.io.In.ReadInt32();		//368
            this.mHeader.unknown_6 = this.io.In.ReadBytes(36);				//372
            this.mHeader.mapName = this.io.In.ReadChars(32);				//408
            this.mHeader.zero_2 = this.io.In.ReadInt32();					//440
            this.mHeader.scenarioPath = this.io.In.ReadChars(32);			//444
            this.mHeader.zero_3 = this.io.In.ReadBytes(224);				//476
            this.mHeader.unknown_7 = this.io.In.ReadInt32();				//700
            this.mHeader.fileCount = this.io.In.ReadInt32();				//704
            this.mHeader.fileTableOffset = this.io.In.ReadInt32();			//708
            this.mHeader.fileTableSize = this.io.In.ReadInt32();			//712
            this.mHeader.filesIndex = this.io.In.ReadInt32();				//716
            this.mHeader.signature = this.io.In.ReadInt32();				//720
            this.mHeader.zero_4 = this.io.In.ReadBytes(1064);				//724
            this.mHeader.foot = this.io.In.ReadChars(4);					//1788
            #endregion

            //Prepare Index Table Header
            #region H2 Index Table Header
            this.io.Position = this.mHeader.indexOffset;
            this.mIHeader.metaStart = this.mHeader.indexOffset + this.mHeader.indexSize;
            this.mIHeader.indexHeaderOffset = this.mHeader.indexOffset;
            this.mIHeader.primaryMagicConstant = this.io.In.ReadInt32();
            this.mIHeader.tagListCount = this.io.In.ReadInt32();
            this.mIHeader.objectIndexOffset = this.io.In.ReadInt32();
            this.mIHeader.scenarioID = this.io.In.ReadInt32();
            this.mIHeader.matchGlobalsID = this.io.In.ReadInt32();
            this.mIHeader.unknown = this.io.In.ReadInt32();
            this.mIHeader.objectCount = this.io.In.ReadInt32();
            this.mIHeader.tags = this.io.In.ReadChars(4);
            this.mIHeader.objectStart = (this.mHeader.indexOffset + 32) + (12 * this.mIHeader.tagListCount);
            this.io.Position = mIHeader.objectStart + 8;
            this.mIHeader.matgOffset = this.io.In.ReadInt32();
            #endregion

            //Read Tag List
            #region Read Tag List
            this.io.Position = this.mHeader.filesIndex;
            int[] fileOffsets = new int[this.mHeader.fileCount];
            for (int i = 0; i < this.mHeader.fileCount; i++)
                fileOffsets[i] = this.io.In.ReadInt32();
            fileList = new string[mHeader.fileCount];
            for (int i = 0; i < this.mHeader.fileCount; i++)
            {
                this.io.Position = fileOffsets[i] + this.mHeader.fileTableOffset;
                string file = string.Empty;
                char c = this.io.In.ReadChar();
                while (c != '\0')
                {
                    file += c;
                    c = io.In.ReadChar();
                }
                fileList[i] = file;
            }
            #endregion

            //Read Tag Items
            #region Read Tag Items
            tags = new TAG_LIST_ENTRY[this.mIHeader.tagListCount];
            for (int i = 0; i < this.mIHeader.tagListCount; i++)
            {
                tags[i] = new TAG_LIST_ENTRY();
                this.io.Position = (this.mHeader.indexOffset + 32) + (i * 12);

                tags[i].objectClass = this.io.In.ReadChars(4);
                tags[i].parentClass = this.io.In.ReadChars(4);
                tags[i].grandparentClass = this.io.In.ReadChars(4);
            }
            #endregion

            //Read Objects and create Index Entries
            #region Read Objects and create Index Entries
            string fileName = null;
            objects = new OBJECT_ENTRY[this.mIHeader.objectCount];
            indexEntries = new IndexEntry[this.mIHeader.objectCount];
            for (int i = 0; i < this.mIHeader.objectCount; i++)
            {
                objects[i] = new OBJECT_ENTRY();
                IO.MapStream.Position = this.mIHeader.objectStart + (16 * i);

                objects[i].tag = this.io.In.ReadChars(4);
                objects[i].id = this.io.In.ReadInt32();
                objects[i].offset = this.io.In.ReadInt32();
                objects[i].metaLength = this.io.In.ReadInt32();

                fileName = FileCount <= i ? string.Empty : fileList[i];
                indexEntries[i] = new IndexEntry(objects[i], this.mIHeader.matgOffset - this.mIHeader.metaStart, fileName);
                indexEntries[i].Magic = SecondaryMagic;
            }
            #endregion

            //Load String IDs
            #region Read String IDs
            this.io.Position = this.mHeader.stringTableOffset;
            String[] falseStrings = new String(io.In.ReadChars(this.mHeader.stringTableSize)).Split('\0');
            stringIDs = new String[this.mHeader.stringsCount];
            #endregion

            //Load String 128s
            #region Read String 128s
            this.io.Position = this.mHeader.strings128TableOffset;
            String falseStrings128 = new String(io.In.ReadChars(128 * this.mHeader.stringsCount));
            string128s = new String[this.mHeader.stringsCount];
            #endregion

            //Fix Strings
            #region Fix Strings
            for (int i = 0; i < this.mHeader.stringsCount; i++)
            {
                //String ID's
                StringIDs[i] = falseStrings[i];

                //String 128's
                Int32 offset128 = 128 * i;

                for (int x = 0; x < 128; x++)
                {
                    if (falseStrings128[offset128 + x] != '\0')
                        string128s[i] += falseStrings128[offset128 + x];
                }
            }
            #endregion

            //Load SBSP  and LTMP Data
            #region Load SBSP and LTMP Data
            this.scenarioEntry = GetEntryByIdent(ScenarioID);
            int Offset = scenarioEntry.Offset + 528; this.io.Position = Offset;
            int Count = this.io.In.ReadInt32(); int Pointer = this.io.In.ReadInt32();
            int TagBlockOffset = Pointer - SecondaryMagic;
            for (int i = 0; i < Count; i++)
            {
                //Go to Position
                this.io.Position = TagBlockOffset + (i * 68);

                //Get SBSP Data
                int SBSP_Offset = this.io.In.ReadInt32();
                int SBSP_Size = this.io.In.ReadInt32();
                int Magic = this.io.In.ReadInt32() - SBSP_Offset;
                this.io.Position += 8;
                int sbspId = this.io.In.ReadInt32();

                //Set SBSP Entry
                if (sbspId != -1)
                {
                    Int32 SbspIdIndex = GetIndexByIdent(sbspId);
                    indexEntries[SbspIdIndex].Offset = SBSP_Offset;
                    indexEntries[SbspIdIndex].Size = SBSP_Size;
                    indexEntries[SbspIdIndex].Magic = Magic;
                }

                //Get LTMP Ident
                this.io.Position = TagBlockOffset + (i * 68) + 28;
                int ltmpId = this.io.In.ReadInt32();
                this.io.Position = SBSP_Offset + 8;

                //Get LTMP Data
                int LTMP_Offset = 0;
                int LTMP_Size = 0;
                if (SBSP_Offset != 0 && SBSP_Offset != -1)
                {
                    LTMP_Offset = this.io.In.ReadInt32() - Magic;
                    LTMP_Size = SBSP_Size + SBSP_Offset - LTMP_Offset;
                }

                //Set LTMP Entry
                if (ltmpId != -1)
                {
                    int LtmpIdIndex = GetIndexByIdent(ltmpId);
                    indexEntries[LtmpIdIndex].Offset = LTMP_Offset;
                    indexEntries[LtmpIdIndex].Size = LTMP_Size;
                    indexEntries[LtmpIdIndex].Magic = Magic;
                }
            }
            #endregion

            //Close Map
            this.io.CloseIO();
        }
        public IAsyncResult BeginReload(AsyncCallback callback, object state)
        {
            //Create
            AsyncOperation reloadOperation = AsyncOperationManager.CreateOperation(state);
            ReloadMapWorker worker = new ReloadMapWorker(Reload);
            return worker.BeginInvoke(callback, worker);
        }
        public void EndReload(IAsyncResult ar)
        {
            //Get Worker...
            if (ar.AsyncState is ReloadMapWorker)
            {
                //End
                ReloadMapWorker reloadWorker = (ReloadMapWorker)ar.AsyncState;
                reloadWorker.EndInvoke(ar);
            }
        }
        public int GetIndexByIdent(Int32 Ident)
        {
            return new List<IndexEntry>(indexEntries).FindIndex(entry => entry.Ident == Ident);
        }
        public IndexEntry GetEntryByIdent(Int32 Ident)
        {
            return new List<IndexEntry>(indexEntries).Find(entry => entry.Ident == Ident);
        }
        public IndexEntry GetEntryByFilename(string fileName)
        {
            //Split
            string[] parts = fileName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

            //Check
            if (parts.Length == 2)
                return new List<IndexEntry>(indexEntries).Find(e => e.FileName == parts[0] && e.Class == parts[1]);
            return null;
        }
        public int GetStringIDFromName(string name)
        {
            for (short i = 0; i < StringsCount; i++)
            {
                string s = stringIDs[i];
                if (s == name)
                {
                    int loword = i;
                    int hiword = (s.Length << 8) << 16;
                    return loword | hiword;
                }
            }

            return 0;
        }

        private delegate void LoadMapFromFileWorker(String fileLocation);
        private delegate void LoadMapFromStreamWorker(Stream stream);
        private delegate void ReloadMapWorker();
    }
}